/* 
* Copyright (c) Harry Pierson. 
*
* This source code is subject to terms and conditions of the Microsoft 
* Permissive License. A copy of the license can be found in the License.html 
* file at the root of this distribution. If you cannot locate the  Microsoft 
* Permissive License, please send an email to harry@devhawk.net. By using this 
* source code in any fashion, you are agreeing to be bound by the terms of the 
* Microsoft Permissive License.
* 
* You must not remove this notice, or any other, from this software.
*/

using System;

namespace HawkWiki.WikiRenderer
{

	public class WikiRenderer
	{
		WikiScanner _scanner;
		WikiToken   _curToken;
		
		public WikiRenderer(System.IO.TextReader tr)
		{
			_scanner = new WikiScanner(tr);
		}

		private bool IsImage(string url)
		{
			string ext = System.IO.Path.GetExtension(url).ToLower();
			return (ext == ".gif" || ext == ".jpeg" || ext == ".jpg" || ext == ".png");
		}

		private enum LineState
		{
			Normal,
			Monospace,
			Indent,
			Heading,
			Rule,
			List
		}

		private void ProcessLine(WikiWriter tw)
		{
			bool bold = false;
			bool italics = false;

			while (_curToken != null && _curToken.TokenType != WikiTokenType.tEOL)
			{
				switch (_curToken.TokenType)
				{
					case WikiTokenType.tBold:
						bold = !bold;
						tw.WriteBold(bold);
						break;
					case WikiTokenType.tItalic:
						italics = !italics;
						tw.WriteItalics(italics);
						break;
					case WikiTokenType.tBoldItalic:
						bold = !bold;
						tw.WriteBold(bold);
						italics = !italics;
						tw.WriteItalics(italics);
						break;

					case WikiTokenType.tFootnote:
						tw.WriteFootnote(_curToken.Text);
						
						break;
					case WikiTokenType.tFootnoteLink:
						tw.WriteFootnoteLink(_curToken.Text);
						break;
					case WikiTokenType.tURL:
					{
						string url = _curToken.Text.ToLower();
						if (url.StartsWith("mailto:"))
							tw.WriteHyperlink(_curToken.Text, _curToken.Text.Substring(7));
						else if (IsImage(url))
							tw.WriteImage(_curToken.Text);
						else
							tw.WriteHyperlink(_curToken.Text, _curToken.Text);
						break;
					}
					case WikiTokenType.tWikiWord:
						tw.WriteWikiWord(_curToken.Text);
						break;
					default:
						tw.WriteText(_curToken.Text);
						break;
				}
				NextToken();
			}

			if (bold) tw.WriteBold(false);
			if (italics) tw.WriteItalics(false);

		}

		private WikiToken NextToken()
		{
			_curToken = _scanner.Scan();
			_prevState = _curState;
			return _curToken;
		}

		LineState _curState;
		LineState _prevState;
		System.Collections.Stack _listStack;
		int _stateCount;

		private void CleanupState(WikiWriter tw)
		{
			switch (_prevState)
			{
				case LineState.Monospace:
					tw.WriteMonospaced(false);
					break;
				case LineState.Indent:
					for (int i = 0; i < _stateCount; i++)
						tw.WriteIndent(false);
					break;
				case LineState.List:
					while (_listStack.Count > 0)
						tw.WriteList((WikiWriter.ListType)_listStack.Pop(), false);
					break;
				default:
					//normal and heading have no state across lines
					break;
			}

			//reset state tracking 
			_listStack.Clear();
			_stateCount = 0;
		}

		private int ListSpaces()
		{
			if (_curToken.TokenType != WikiTokenType.tBulletList && _curToken.TokenType != WikiTokenType.tNumberList)
				throw new ApplicationException("Can't call ListSpaces except with a list token");

			return _curToken.Text.Length - _curToken.Text.TrimStart(' ').Length;
		}

		private WikiWriter.ListType ListTag()
		{
			switch (_curToken.TokenType)
			{
				case WikiTokenType.tBulletList:
					return WikiWriter.ListType.Bulleted;
				case WikiTokenType.tNumberList:
					return WikiWriter.ListType.Numbered;
				default:
					throw new ApplicationException("Can't call ListTag except with a list token");
			}
		}


		public void ProcessWiki(WikiWriter tw)
		{
			//init state tracking
			_curState = LineState.Normal;
			_prevState = LineState.Normal;
			_listStack = new System.Collections.Stack();
			_stateCount  = 0;

			while (NextToken() != null)
			{
				//save the previous line state
				

				//Handle any of the line start tokens
				switch (_curToken.TokenType)
				{
					case WikiTokenType.tEOL:
						_curState = LineState.Normal;
						CleanupState(tw);
						continue;
					case WikiTokenType.tMonospace:
						//If the previous line was monospace, ignore this token
						//if the previous line was not monospace, cleans up previous line state and write a <pre> tag
						//either way, don't enclose the line in any tag
						_curState = LineState.Monospace;
						if (_curState != _prevState)
						{
							CleanupState(tw);
							tw.WriteMonospaced(true);
						}
						else
						{
							tw.WriteNewline();
						}
						NextToken();
						break;
					case WikiTokenType.tRule:
						//clean up any previous line state, ignore all tokens until the tEOL token, and write a <hr/>
						_curState = LineState.Rule;
						CleanupState(tw);
						tw.WriteRule();
						do
						{
							NextToken();
						}
						while (_curToken != null && _curToken.TokenType != WikiTokenType.tEOL); 
						break;

					case WikiTokenType.tIndent:
						//if the previous line had an equal number of indents, ignore the tag
						//if the previous line had less or more indents, write out as many <blockquote> or </blockquotes> to make up the difference
						//Either way, enclose the line in a <p> tag
						_curState = LineState.Indent;
						if (_curState != _prevState)
						{
							CleanupState(tw);
							_stateCount = _curToken.Text.Length;
							for (int i = 0; i < _stateCount; i++)
								tw.WriteIndent(true);
						}
						else
						{
							int diff = _curToken.Text.Length - _stateCount;

							if (diff > 0)
							{
								//add more blockquote tags
								for (int i = 0; i < diff; i++)
									tw.WriteIndent(true);
							}
							else if (diff < 0)
							{
								//write out blockquote end tags
								for (int i = 0; i > diff; i--)
									tw.WriteIndent(false);
							}

							_stateCount = _curToken.Text.Length;
						}
						tw.WriteParagraph(true);
						NextToken();
						break;

					case WikiTokenType.tHeading:
						//clean up previous line state, and enclose the line in a header tag
						_curState = LineState.Heading;
						CleanupState(tw);
						_stateCount = _curToken.Text.Length;
						tw.WriteHeading(_stateCount, true);
						NextToken();
						break;
					case WikiTokenType.tNumberList:
					case WikiTokenType.tBulletList:
						//if the previous line had the same level of list (regardless if it matched type), ignore tag
						//else add or remove list tags (ul or ol as appropriate) to bring the level correct
						//either way, enclose the line in a <li> tag
						_curState = LineState.List;
						if (_curState != _prevState)
						{
							CleanupState(tw);
							int count = ListSpaces();
							for (int i = 0; i < count; i++)
							{
								WikiWriter.ListType type = ListTag();
								_listStack.Push(type);
								tw.WriteList(type, true);
							}
						}
						else
						{
							int diff = ListSpaces() - _listStack.Count;

							if (diff > 0)
							{
								//add more list tags
								for (int i = 0; i < diff; i++)
								{
									WikiWriter.ListType type = ListTag();
									_listStack.Push(type);
									tw.WriteList(type, true);
								}
							}
							else if (diff < 0)
							{
								//end lists by popping off the stack
								for (int i = 0; i > diff; i--)
									tw.WriteList((WikiWriter.ListType)_listStack.Pop(), false);
							}

						}
						tw.WriteListItem(true);
						NextToken();
						break;
					default:
						_curState = LineState.Normal;
						if (_curState != _prevState)
							CleanupState(tw);
						tw.WriteParagraph(true);
						break;
				}

				ProcessLine(tw);

				//Handle end of line
				switch (_curState)
				{
					case LineState.Heading:
						tw.WriteHeading(_stateCount, false);
						break;
					case LineState.Indent:
					case LineState.Normal:
						tw.WriteParagraph(false);
						break;
					case LineState.List:
						tw.WriteListItem(false);
						break;
				}
			}

			CleanupState(tw);
		}
	}
}
